using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;

using HappyTemplate.BuiltInFunctions;

namespace HappyTemplate
{
	public abstract class NonTerminalBase
	{
		readonly SourceLocation _startsAt;

		public SourceLocation StartsAt { get { return _startsAt; } }

		protected NonTerminalBase(SourceLocation startsAt)
		{
			_startsAt = startsAt;
		}

		//internal abstract void Walk(TemplateStageBase stage);
	}

	public abstract class ExecutableNonTerminalBase : NonTerminalBase
	{
		protected ExecutableNonTerminalBase(SourceLocation startsAt)
			: base(startsAt)
		{
		}

		internal abstract void Execute(TemplateContext context, TextWriter to);

	}

	public class TemplateSet
	{
		readonly Dictionary<string, Template> _templates = new Dictionary<string, Template>();
		private readonly TemplateContext _context;

		readonly Dictionary<string, Lookup> _lookups = new Dictionary<string, Lookup>();

		public TemplateContext Context { get { return _context; } }

		internal TemplateSet(
			IEnumerable<Lookup> lookups, 
			IEnumerable<Template> templates, 
			Dictionary<string, BuiltInFunctionBase> builtins)
		{
			_context = new TemplateContext(this, builtins);
			foreach (Lookup l in lookups)
				_lookups.Add(l.Name, l);

			foreach (Template t in templates)
				_templates.Add(t.Name, t);
		}

		internal Template GetTemplate(string templateName)
		{
			Template retval;
			return _templates.TryGetValue(templateName, out retval) ? retval : null;
		}

		internal Lookup GetLookup(Identifier key)
		{
			Lookup retval;
			return _lookups.TryGetValue(key.Text, out retval) ? retval : null;
		}

		public void ExecuteTemplate(TextWriter outputWriter, string name, params object[] arguments)
		{
			if(outputWriter == null)
				throw new ArgumentNullException("outputWriter");
			if(name == null)
				throw new ArgumentNullException("name");

			int originalStackSize = _context.StackSize;
			//Lookup the template; throw exception if it doesn't exist
			Template template = GetTemplate(name);
			if(template == null)
				throw new ArgumentException(
					StringHelp.Format(Resources.ExecutionErrorMessages.ObjectDoesNotExist_Identifier, name),
					"name");

			//Validate number of arguments; throw exception if invalid
			if(template.Parameters.Length != arguments.Length)
				throw new ArgumentException(
					StringHelp.Format(Resources.ExecutionErrorMessages.IncorrectNumberOfArguments_ObjectName_Expected_Actual, 
						template.Name, template.Parameters.Length, arguments.Length));

			_context.Push(template.StartsAt);

			//Copy parameter names and values to the context.
			for(int i = 0; i < arguments.Length; ++i)
				_context[template.Parameters[i].Name] = arguments[i];

			try
			{
				template.Execute(_context, outputWriter);	
			} 
			finally
			{
				//Return stack to it's orinal state before (attempted) execution.
				//we have to use a while here because the stack might have 
				//been pushed several times before an exception has occurred.
				while(_context.StackSize > originalStackSize)
					_context.Pop();
			}

		}

		//internal void Walk(TemplateStageBase stage)
		//{
		//    stage.BeforeTemplateSet(this);
		//    foreach (var dictionaryItem in _templates)
		//        dictionaryItem.Value.Walk(stage);
		//    stage.AfterTemplateSet(this);
		//}
	}

	public class Template : ExecutableNonTerminalBase
	{
		readonly ExecutableNonTerminalBase[] _nonTerminals;
		readonly string _name;
		private readonly TemplateParameter[] _parameters;

		internal string Name { get { return _name; } }
		internal TemplateParameter[] Parameters { get { return _parameters; } }
		internal ExecutableNonTerminalBase[] NonTerminals { get { return _nonTerminals; } }

		internal Template(SourceLocation location, string name, TemplateParameter[] parameters, ExecutableNonTerminalBase[] terminals)
			: base(location)
		{
			_name = name;
			_parameters = parameters;
			_nonTerminals = terminals;
		}

		internal override void Execute(TemplateContext context, TextWriter to)
		{
			context.Push(base.StartsAt);
			foreach (ExecutableNonTerminalBase s in _nonTerminals)
				s.Execute(context, to);
			context.Pop();
		}

		//internal override void Walk(TemplateStageBase stage)
		//{
		//    stage.BeforeTemplate(this);
		//    stage.BeforeTemplateParameters(this);
		//    foreach(TemplateParameter p in _parameters)
		//        p.Walk(stage);
		//    stage.AfterTemplateParameters(this);
		//    stage.BeforeTemplateNonTerminals(this);
		//    foreach(ExecutableNonTerminalBase nt in _nonTerminals)
		//        nt.Walk(stage);
		//    stage.AfterTemplateNonTerminals(this);
		//    stage.AfterTemplate();
		//}
	}

	internal class TemplateParameter : NonTerminalBase
	{
		private readonly string _name;
		public string Name { get { return _name; } }

		public TemplateParameter(SourceLocation loc, string text)
			: base(loc)
		{
			_name = text;
		}
		//internal override void Walk(TemplateStageBase stage)
		//{
		//    stage.TemplateParameter(this);
		//}
	}



	/// <summary>
	/// This is just a place-holder non-terminal used so
	/// that we can continue parsing after an unexpected token
	/// is enountered.
	/// </summary>
	/// <remarks>
	/// Normally, this would never be included as part of
	/// the parse tree unless we are also adding an error
	/// to the ParserErrorCollector instance as well. 
	///  </remarks>
	internal class UnexpectedToken : ExecutableNonTerminalBase
	{
		public UnexpectedToken(SourceLocation startsAt)
			: base(startsAt)
		{

		}

		internal override void Execute(TemplateContext context, TextWriter to)
		{
			TemplateExecutionException.ThrowInternal(this.StartsAt, "Cannot write UnexpectedToken (you shouldn't be seeing this)");
		}
		//internal override void Walk(TemplateStageBase stage)
		//{
		//    //do nothing
		//}
	}

	internal class VerbatimSection : ExecutableNonTerminalBase
	{
		readonly string _output;

		public VerbatimSection(SourceLocation startsAt, string output)
			: base(startsAt)
		{
			_output = output;
		}

		internal override void Execute(TemplateContext context, TextWriter to)
		{
			to.Write(_output);
		}

		//internal override void Walk(TemplateStageBase stage)
		//{
		//    stage.VerbatimSection(this);
		//}
	}

	/// <summary>
	/// This class only exists to give all of our expansions (literal, variable and tempalte)
	/// a common base class.
	/// </summary>
	abstract class ExpansionBase : ExecutableNonTerminalBase
	{
		protected ExpansionBase(SourceLocation loc)
			: base(loc)
		{
		}

		internal abstract object CreateArgument(TemplateContext context);
	}

	class NullExpansion : ExpansionBase
	{
		public NullExpansion(SourceLocation loc) : base(loc)
		{
		}

		internal override void Execute(TemplateContext context, TextWriter to)
		{
			//intentionally left blank
		}

		internal override object CreateArgument(TemplateContext context)
		{
			return null;
		}
	}

	class LiteralExpansion<T> : ExpansionBase
	{
		private readonly T _value;

		public T Value { get { return _value; } }

		public LiteralExpansion(SourceLocation loc, T value)
			: base(loc)
		{
			_value = value;
		}

		internal override void Execute(TemplateContext context, TextWriter to)
		{
			to.Write(_value);
		}
		internal override object CreateArgument(TemplateContext context)
		{
			return _value;
		}
	}

	
	abstract class NamedExpansionBase : ExpansionBase
	{
		readonly protected Identifier _identifier;

		public Identifier Identifier { get { return _identifier; } }

		protected NamedExpansionBase(Identifier identifier)
			: base(identifier.Location)
		{
			_identifier = identifier;
		}

		//internal override void Walk(TemplateStageBase stage)
		//{
		//    stage.Expansion(this);
		//}
	}


	internal class VariableExpansion : NamedExpansionBase
	{
		public VariableExpansion(Identifier identifier) : base(identifier) { }

		internal override void Execute(TemplateContext context, TextWriter to)
		{
			object item = context.FindVariable(_identifier);

			if (item == null)
				to.Write(TemplateContext.NullText);
			else
			{
				ExecutableNonTerminalBase executable = item as ExecutableNonTerminalBase;
				if (executable != null)
					executable.Execute(context, to);
				else
					//if (_identifier.Parts.Length > 0)  <-- what's this for?
					to.Write(item.ToString());
			}
		}

		internal override object CreateArgument(TemplateContext context)
		{
			return context.FindVariable(_identifier);
		}
	}

	class TemplateExpansion : NamedExpansionBase
	{
		private readonly ExpansionBase[] _arguments;
		public ExpansionBase[] Arguments
		{
			get
			{
				return _arguments;
			}
		}

		public TemplateExpansion(Identifier identifier, ExpansionBase[] arguments)
			: base(identifier)
		{
			_arguments = arguments;
		}

		internal override void Execute(TemplateContext context, TextWriter to)
		{
			if (_identifier.Parts.Length > 1)
				TemplateExecutionException.ThrowTemplateNamesCannotBeMutlipart(_identifier.Location);

			//Look up the template, throw an exception if it doesn't exist
			Template template = context.TemplateSet.GetTemplate(_identifier.Text);
			if (template == null)
				TemplateExecutionException.ThrowTemplateDoesNotExist(_identifier);
			else
			{
				//Ensure that the number of arguments specified matches the number 
				//of parameters specified in the template definition
				if (template.Parameters.Length != _arguments.Length)
					TemplateExecutionException.ThrowIncorrectNumberOfArguments(_identifier, template.Parameters.Length,
						_arguments.Length);

				//Push the context state
				context.Push(this.StartsAt);

				//For each argument, store it in the pushed context under the appropriate name
				for (int i = 0; i < template.Parameters.Length; ++i)
					context[template.Parameters[i].Name] = _arguments[i].CreateArgument(context);

				//Expand the template:
				template.Execute(context, to);

				//Pop the context state
				context.Pop();
			}
		}

		internal override object CreateArgument(TemplateContext context)
		{
			StringWriter sw = new StringWriter();	
			this.Execute(context, sw);
			return sw.ToString();
		}
	}

	class AnonymousTemplateExpansion : ExpansionBase
	{
		readonly ExecutableNonTerminalBase[] _nonTerminals;

		public AnonymousTemplateExpansion(SourceLocation loc, ExecutableNonTerminalBase[] nonTerminals)
			: base(loc)
		{
			_nonTerminals = nonTerminals;
		}

		internal override void Execute(TemplateContext context, TextWriter to)
		{
			context.Push(base.StartsAt);
			foreach (ExecutableNonTerminalBase s in _nonTerminals)
				s.Execute(context, to);
			context.Pop();
		}

		internal override object CreateArgument(TemplateContext context)
		{
			StringWriter sw = new StringWriter();
			this.Execute(context, sw);
			return sw.ToString();
		}
	}

	class BuiltInFunctionExpansion : NamedExpansionBase
	{
		readonly ExpansionBase[] _arguments;
		public BuiltInFunctionExpansion(Identifier identifier, ExpansionBase[] arguments)
			: base(identifier)
		{
			_arguments = arguments;
		}

		internal override void Execute(TemplateContext context, TextWriter to)
		{
			object retval = CreateArgument(context);
			if(retval != null)
				to.Write(retval.ToString());
		}

		internal override object CreateArgument(TemplateContext context)
		{
			BuiltInFunctionBase bif = context.GetBuiltInFunction(_identifier.Text);
			DebugAssert.IsNotNull(bif);


			//Ensure that the number of arguments specified matches the number 
			//of parameters specified in the template definition
			if (bif.MaxArgCount < _arguments.Length || bif.MinArgCount > _arguments.Length)
				TemplateExecutionException.ThrowIncorrectNumberOfArguments(_identifier, bif.MinArgCount, 
					bif.MaxArgCount, _arguments.Length);

			ArgumentCollection args = new ArgumentCollection(_arguments.Length);
			for (int i = 0; i < _arguments.Length; ++i)
			{
				object argument = _arguments[i].CreateArgument(context);
				if (bif.ForceNonNullAllArguments && argument == null)
					TemplateExecutionException.ThrowArgumentNull(base.StartsAt, i, bif.Name);

				args.Add(_arguments[i].CreateArgument(context));
			}

			try
			{
				return bif.Invoke(args);
			}
			catch(Exception e)
			{
				throw TemplateExecutionException.NewBuiltinFuncitonException(this.StartsAt, bif.Name, e);
			}
		}
	}



	class LookupExpansion : ExpansionBase
	{
		readonly Identifier _identifier;
		readonly ExpansionBase _inputExpansion;
		//string _outputValue;

		public ExpansionBase InputExpansion { get { return _inputExpansion; } }

		public LookupExpansion(Identifier identifier, ExpansionBase inputExpansion)
			: base(identifier.Location)
		{
			DebugAssert.AreEqual(1, identifier.Parts.Length, "cannot be multipart identifier");
			_identifier = identifier;
			_inputExpansion = inputExpansion;
		}

		internal override void Execute(TemplateContext context, TextWriter to)
		{
			//if(_outputValue == null)
			string outputValue;
			Lookup le = context.TemplateSet.GetLookup(_identifier);
			if(le == null)
				TemplateExecutionException.ThrowIdentifierNotFound(_identifier);
			StringWriter inputSw = new StringWriter();
			_inputExpansion.Execute(context, inputSw);
			string inputValue = inputSw.ToString();

			if (!le.TryGetValue(inputValue, out outputValue))
				if (le.DefaultValue == null)
					TemplateExecutionException.ThrowInputValueDoesNotExistInLookup(_identifier, inputValue);
				else
					outputValue = le.DefaultValue;

			to.Write(outputValue);
		}

		internal override object CreateArgument(TemplateContext context)
		{
			StringWriter sw = new StringWriter();
			this.Execute(context, sw);
			return sw.ToString();
		}
	}

	class SetStatement : ExecutableNonTerminalBase
	{
		public enum SetType
		{
			Local,
			Global
		}

		readonly Identifier _identifier;
		readonly ExpansionBase _value;
		readonly SetType _type;

		public SetStatement(SourceLocation startsAt, SetType type, Identifier identifier, ExpansionBase value) 
			: base(startsAt)
		{
			_identifier = identifier;
			_value = value;
			_type = type;
		}

		internal override void Execute(TemplateContext context, TextWriter to)
		{
			switch(_type)
			{
			case SetType.Local:
				context[_identifier.Text] = _value.CreateArgument(context);
				break;
			case SetType.Global:
				context.SetGlobal(_identifier.Text, _value.CreateArgument(context));
				//Indexer by default operates at the global level.
				break;
			default:
				throw new UnhandledCaseSourceException(base.StartsAt);
			}
		}
	}

	class IfStatement : ExecutableNonTerminalBase
	{
		readonly ExpansionBase _value;
		readonly ExecutableNonTerminalBase[] _trueNonTerminals;
		readonly ExecutableNonTerminalBase[] _falseNonTerminals;
		readonly bool _inverted;

		public IfStatement(bool inverted, ExpansionBase  value, ExecutableNonTerminalBase[] trueNonTerminals, ExecutableNonTerminalBase[] falseNonTerminals)
			: base(value.StartsAt)
		{
			_inverted = inverted;
			_value = value;
			_trueNonTerminals = trueNonTerminals;
			_falseNonTerminals = falseNonTerminals;
		}

		//internal override void Walk(TemplateStageBase stage)
		//{
		//    stage.BeforeIfStatement(this);
		//    foreach(ExecutableNonTerminalBase nt in _trueNonTerminals)
		//        nt.Walk(stage);
		//    stage.AfterIfStatement(this);
		//}

		internal override void Execute(TemplateContext context, TextWriter to)
		{
			if (TemplateHelp.IsTrue(_value.CreateArgument(context)) ^ _inverted)
				foreach (ExecutableNonTerminalBase s in _trueNonTerminals)
					s.Execute(context, to);
			else
				foreach (ExecutableNonTerminalBase s in _falseNonTerminals)
					s.Execute(context, to);
		}
	}

	class ForEachStatement : ExecutableNonTerminalBase
	{
		readonly Identifier _loopVar;
		readonly ExpansionBase _enumerable;
		readonly ExecutableNonTerminalBase[] _nonTerminals;
		private readonly ExpansionBase _between;

		public Identifier LoopVar { get { return _loopVar; }}
		public ExpansionBase Enumerable { get { return _enumerable; } }
		public ExecutableNonTerminalBase[] NonTerminals { get { return _nonTerminals;  } }
		public ExpansionBase Between { get { return _between; }}

		public ForEachStatement(SourceLocation loc, Identifier loopVar, ExpansionBase enumerable, ExpansionBase between, ExecutableNonTerminalBase[] nonTerminals)
			: base(loc)
		{
			_loopVar = loopVar;
			_enumerable = enumerable;
			_between = between;
			_nonTerminals = nonTerminals;
		}

		//internal override void Walk(TemplateStageBase stage)
		//{
		//    stage.BeforeForEachStatement(this);
		//    foreach (ExecutableNonTerminalBase nt in _nonTerminals)
		//        nt.Walk(stage);		
		//    stage.AfterForEachStatement(this);
		//}

		internal override void Execute(TemplateContext context, TextWriter to)
		{
			object item = _enumerable.CreateArgument(context);
			if (item == null)
				to.Write(TemplateContext.NullText);
			else
			{
				IEnumerable enumerable = item as IEnumerable;
				if (enumerable == null)
					TemplateExecutionException.ThrowDoesNotImplementIEnumerable(this.StartsAt);
				else
				{
					IEnumerator enumerator = enumerable.GetEnumerator();
					enumerator.Reset();
					if(enumerator.MoveNext())
					{
						context.Push(this.StartsAt);
						bool more = true;
						do
						{
							context[_loopVar.Text] = enumerator.Current;

							foreach (ExecutableNonTerminalBase s in _nonTerminals)
								s.Execute(context, to);

							more = enumerator.MoveNext();

							if(more && _between != null)
								_between.Execute(context, to);

						} while (more);
						context.Pop();
					}
				}
			}
		}
	}

	class Lookup : NonTerminalBase
	{
		private readonly Dictionary<string, string> _values;
		private readonly Identifier _identifier;
		private readonly string _defaultValue;

		public string Name { get { return _identifier.Text; } }
		public string DefaultValue { get { return _defaultValue;  } }

		internal Lookup(Identifier ident, Dictionary<string, string> values, string defaultValue)
			: base(ident.Location)
		{
			_identifier = ident;
			_values = values;
			_defaultValue = defaultValue;
		}


		public bool TryGetValue(string key, out string value)
		{
			return _values.TryGetValue(key, out value);
		}
	}
}
